#! /usr/bin/perl
# eBK9cFECLGQ
use strict;

my $copyright = <<EOT;
// Copyright (c) 2009 Helmar Wodtke. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// The MIT License is an OSI approved license and can
// be found at
//   http://www.opensource.org/licenses/mit-license.php

EOT

my @PdfDrawer = (
  "  Stack stacks.Stack;",
  "  Ops map[string]func(pd *PdfDrawerT);",
  "  CurrentPoint [][]byte;",
  "  ConfigD *DrawerConfigT;",
  "  TConfD *TextConfigT;",
  "  Write *util.OutT;",
);

while (<DATA>) {
  last if /^%%\n/;
  s/%copyright%/$copyright/;
  print $_;
}

my ($component, $interface, $prefix);
my %ops;
my @ops;
my @variables;
my %methods;
my $current;

sub writeInterface() {
  return if not defined $interface;
  push @PdfDrawer, "  $component $interface;"
    if $component ne "*";
  print "type $interface interface {\n";
  for (sort keys %methods) {
     print "  $_($methods{$_});\n";
  }
  print "}\n\n";

  if (scalar(@variables)) {
    print "type $interface"."T struct {\n";
    print "  $_;\n" for @variables;
    print "}\n\n";
   
    for (@variables) {
      my @t = split/ /;
      my $m = "$prefix$t[0]";
      if (defined $methods{$m}) {
        print "func (t *$interface"."T) $m($methods{$m}) {\n";
        print "  t.$t[0] = $t[1](a);\n";
        print "}\n";
      }
    }
    print "\n";
  }

  if (scalar keys %ops) {
   for (sort keys %ops) {
     my $c = $ops{$_};
     while ($c =~ s/%([^%]+)%/$ops{$1}/g) {}
     push @ops, "  \"$_\": func(pd *PdfDrawerT) {\n$c  }";
   }
  }

  %ops = %methods = ();
  @variables = ();
}

while (<DATA>) {
  last if /^%%\n/;
  if (/^=\s+(\S+)\s+(\S+)\s+(\S+)/) {
    my ($c, $i, $p) = ($1, $2, $3);
    writeInterface;
    $component = $c;
    $interface = $i;
    $prefix = $p;
    next;
  }
  if (/^\s+\+(\s.*)$/) {
    $ops{$current} .= "  $1\n";
    next;
  }
  if (/^\s+\+(\S+)\s*$/) {
    $ops{$current} .= "%$1%";
    next;
  }
  if (/^(\S+)\s+(\S+)\s+(\S+)\s+/) {
    my ($op, $m, $c) = ($1, $2, $3);
    $ops{$op} = "";
    $current = $op;
    next if $m eq "*";  # no method, no variable
    my $pop = my $proto = my $gen = "";
    if ($c == 1) {
      $pop = "pd.Stack.Pop()";
      $proto = "a []byte";
    } elsif ($c > 1) {
      $gen = "    a := pd.Stack.Drop($c);\n";
      $pop = "a";
      $proto = "s [][]byte";
    }
    if ($m !~ /^$prefix/) { # is also a variable
      push @variables, "$m string";
      $m = "$prefix$m" if $prefix ne ".";
    }
    $ops{$op} .= "$gen    pd.$component.$m($pop);\n";
    $methods{$m} = "$proto";
    next;
  }
  if (/^\s+V: (.*)$/) {
    push @variables, $1;
    next;
  }
  if (/^\s+M: (\S+)( (.*))?$/) {
    my $k = $1;
    $k =~ tr/~/ /;
    $methods{$k} = $3;
    next;
  }
  /^\s*$/ and next;
  warn "irregular line: $_\n";
}
writeInterface;

print "type PdfDrawerT struct {\n", join("\n", @PdfDrawer), "\n}\n\n";

my $dic = "var PdfOps = map[string]func(pd *PdfDrawerT){\n";
$dic .= join(",\n", @ops). ",\n}\n\n";
print $dic;

print join('', <DATA>);

__DATA__
%copyright%
package graf

// WARNING: This file is automatically generated!
//          It makes no sense to change anything here.

import (
  "util";
  "strm";
  "fancy";
  "ps";
)

type DrawerColor interface {
  RGB(rgb [][]byte) string;
  CMYK(cmyk [][]byte) string;
  Gray(g []byte) string;
}

%%

= Draw Drawer .
cm  Concat    6
  +  pd.CurrentPoint = a[4:6];
m   MoveTo    2
  +  pd.CurrentPoint = a;
l   LineTo    2
  +  pd.CurrentPoint = a;
c   CurveTo   6
  +  pd.CurrentPoint = a[4:6];
v   *         *
  +  c := pd.CurrentPoint;
  +  a := pd.Stack.Drop(4);
  +  pd.Draw.CurveTo([][]byte{c[0], c[1], a[0], a[1], a[2], a[3]});
  +  pd.CurrentPoint = a[2:4];
y   *         *
  +  a := pd.Stack.Drop(4);
  +  pd.Draw.CurveTo([][]byte{a[0], a[1], a[2], a[3], a[2], a[3]});
  +  pd.CurrentPoint = a[2:4];
h   ClosePath 0
  +  pd.CurrentPoint = nil;
re  Rectangle 4
  +  pd.CurrentPoint = nil;
S   Stroke    0
  +n
s   *         *
  +  pd.Draw.ClosePath();
  +  pd.Draw.Stroke();
  +n
f   Fill      0
  +n
F   Fill      0
  +n
f*  EOFill    0
  +n
B   FillAndStroke 0
  +n
B*  EOFillAndStroke 0
  +n
b   *         *
  +h
  +B
b*  *         *
  +h
  +B*
n   DropPath  0
  +  pd.CurrentPoint = nil;

  M: SetIdentity
  M: CloseDrawing

= Config DrawerConfig Set
rg  SetRGBFill    3
   +  pd.Ops["sc"] = pd.Ops["rg"];
RG  SetRGBStroke  3
   +  pd.Ops["SC"] = pd.Ops["RG"];
g   SetGrayFill   1
   +  pd.Ops["sc"] = pd.Ops["g"];
G   SetGrayStroke 1
   +  pd.Ops["SC"] = pd.Ops["G"];
k   SetCMYKFill   4
   +  pd.Ops["sc"] = pd.Ops["k"];
K   SetCMYKStroke 4
   +  pd.Ops["SC"] = pd.Ops["K"];
  V: FillColor string
  V: StrokeColor string
w   LineWidth     1
J   LineCap       1
j   LineJoin      1
M   MiterLimit    1
i   Flat          1
gs  *             *
   +  // FIXME!
   +  pd.Draw.SetIdentity();
   +  pd.Stack.Pop();

  V: color DrawerColor
  M: SetColors DrawerColor

= TConf TextConfig Set
Tc  CharSpace 1
Tw  WordSpace 1
Tz  Scale     1
TL  Leading   1
Tf  SetFontAndSize 2
Tr  Render    1
Ts  Rise      1

  V: Font string
  V: FontSize string

= Text DrawerText .
Td  TMoveTo    2
TD  *          *
  +  a := pd.Stack.Drop(2);
  +  pd.TConf.SetLeading(util.Bytes(strm.Neg(string(a[1]))));
  +  pd.Text.TMoveTo(a);
Tm  TSetMatrix 6
T*  TNextLine  0
Tj  TShow      1
'   *          *
  +  pd.Text.TNextLine();
  +  pd.Text.TShow(pd.Stack.Pop());
\"  *          *
  +  t := pd.Stack.Drop(3);
  +  pd.TConf.SetWordSpace(t[0]);
  +  pd.TConf.SetCharSpace(t[1]);
  +  pd.Text.TNextLine();
  +  pd.Text.TShow(t[3]);
TJ  TShow      1
BT  *          *
  +  pd.Text.TSetMatrix(nil);
ET  *          *

= Marker DocumentMarker .
MP  *          *
  +  pd.Stack.Pop();
DP  *          *
  +  pd.Stack.Drop(2);
BMC *          *
  +  pd.Stack.Pop();
BDC *          *
  +  pd.Stack.Drop(2);
EMC *          *

%%

func (pd *PdfDrawerT) Interpret(rdr fancy.Reader) {
  for {
    t, _ := ps.Token(rdr);
    if len(t) == 0 {
      break
    }
    if f, ok := pd.Ops[string(t)]; ok {
      f(pd)
    } else {
      pd.Stack.Push(t)
    }
  }
}


// "constructor"

func NewPdfDrawer() *PdfDrawerT {
  r := new(PdfDrawerT);
  r.Stack = stacks.NewStack(1024);
  r.Ops = make(map[string]func(pd *PdfDrawerT));
  for k := range PdfOps {
    r.Ops[k] = PdfOps[k]
  }
  r.ConfigD = new(DrawerConfigT);
  r.Config = r.ConfigD;
  r.TConfD = new(TextConfigT);
  r.TConf = r.TConfD;
  r.Text = r.TConfD;
  r.Write = new(util.OutT);
  return r;
}

// few glue code to get interfaces working.

func (t *DrawerConfigT)  SetCMYKFill(s [][]byte) {
  t.FillColor = t.color.CMYK(s);
}
func (t *DrawerConfigT)  SetCMYKStroke(s [][]byte) {
  t.StrokeColor = t.color.CMYK(s);
}
func (t *DrawerConfigT)  SetGrayFill(a []byte) {
  t.FillColor = t.color.Gray(a);
}
func (t *DrawerConfigT)  SetGrayStroke(a []byte) {
  t.StrokeColor = t.color.Gray(a);
}
func (t *DrawerConfigT)  SetRGBFill(s [][]byte) {
  t.FillColor = t.color.RGB(s);
}
func (t *DrawerConfigT)  SetRGBStroke(s [][]byte) {
  t.StrokeColor = t.color.RGB(s);
}
func (t *DrawerConfigT)  SetColors(hook DrawerColor) {
  t.color = hook;
}

func (t *TextConfigT) SetFontAndSize(a [][]byte) {
  t.Font = string(a[0]);
  t.FontSize = string(a[1]);
}
func (t *TextConfigT) TMoveTo(s [][]byte)    {}
func (t *TextConfigT) TNextLine()            {}
func (t *TextConfigT) TSetMatrix(s [][]byte) {}
func (t *TextConfigT) TShow(a []byte)        {}
